#include "memalloc.h"

void* buf; 
int count = 0; // number of the blocks of memory
enum { true, false } alloc_initialized;
size_t size_buf; // size of buffer
typedef struct mem_block
{
	unsigned id;
	void* begin; // the address of this block
	size_t size;
	int used; // =1 if this block is used now
	struct mem_block* next;
} mem_block;
struct mem_block* head_block = NULL; // the head of list

int ma_init(size_t mem)
{
	if (true == alloc_initialized) // if allocator has been already initialized
	{
		errno = EALREADY;
		printf ("ma_init: %s\n", strerror (errno));
		return 0;
	}
	buf = (void*) malloc (mem); // create a buffer
	if (buf == NULL)
	{
		perror ("ma_init");
		errno = ENOMEM;
		return (0);
	}
	size_buf = mem; // size of buffer
	alloc_initialized = true;
	return 1;
}

void ma_deinit()
{
	mem_block* block = head_block;
 	mem_block* blockc = NULL;
    while (block->next != NULL) 
    {
	blockc = block;
        block = block->next;
        free(blockc);
    }
    free(head_block);
    free (buf);
    alloc_initialized = false;
}

size_t ma_alloc(size_t sz)
{
	if (head_block == NULL) // if we haven't any blocks yet
	{
		head_block = malloc (sizeof (mem_block));
		if (head_block == NULL)
		{
			return (0);
		}
		count++;
		head_block->id = count; // add a first (single) element to the list
		head_block->begin = buf;
		head_block->size = sz;
		head_block->used = 0;
		head_block->next = NULL;
		return head_block->id;
	}
	else if ((head_block->begin - buf) >= sz) //if we have enough memory to put this block the first of all
	{
		mem_block* block = (mem_block*) malloc (sizeof (mem_block));
		if (block == NULL)
		{
			return (0);
		}
		count++;
		block->id = count; // add a first element to the list
		block->begin = buf;
		block->size = sz;
		block->used = 0;
		block->next = head_block;
		head_block = block;
		return head_block->id;
	}
	else	 
	{
		mem_block* block = head_block;
		while ((block->next != NULL) && ((block->next->begin - block->begin - block->size) < sz))
			block = block->next;
		if (block->next == NULL) // if we are at the end of list
			if ((size_buf - (block->begin - buf) - block->size) >= sz) // if we can put block at the end of list
			{
				mem_block* blockc = (mem_block*) malloc (sizeof(mem_block));
				if (blockc == NULL)
				{
					return (0);
				}
				count++;
				blockc->id = count; // add a new element at the end of the list
				blockc->begin = block->begin + block->size;
				blockc->size = sz;
				blockc->used = 0;
				block->next = blockc;
				blockc->next = NULL;
				return blockc->id;
			}
			else // we haven't enough memory to allocate
			{
				errno = ENOMEM;
				printf ("ma_alloc: %s\n", strerror (errno));
				return (0);
			}
		else // we put the block in the inside of list
		{
			mem_block* blockc = (mem_block*) malloc (sizeof(mem_block));
			if (blockc == NULL)
			{
				return (0);
			}
			count++;			
			blockc->id = count; // add a new alement inside of the list
			blockc->begin = block->begin + block->size;
			blockc->size = sz;
			blockc->used = 0;
			blockc->next = block->next;
			block->next = blockc;
			return blockc->id;
		}
	}
}

int ma_free(unsigned id)
{
	if ((id < 0) || (id > count)) // the wrong id
	{
		errno = EFAULT;
		printf ("ma_free: %s\n", strerror (errno));
		return 0;
	}
	if (head_block->id == id) // we found the block with such id
	{
		if (1 == head_block->used ) // this block is used now
		{
			printf ("ma_free: This memory is used.\n");
			return 0;
		}
		mem_block* block = head_block;
		head_block = head_block->next;
		free (block);
		return 1;
	}
	mem_block* block = head_block;
	while ((block->next->id != id) && (block->next != NULL))
		block = block->next;
	if (block->next == NULL) // the wrong id
	{
		errno = EFAULT;
		printf ("ma_free: %s\n", strerror (errno));
		return 0;
	}
	if (1 == block->next->used) // this block is used now
	{
		printf ("ma_free: This memory is used.\n");
		return 0;
	}
	mem_block* blockc = block->next;
	block->next = block->next->next;
	free (blockc);
	return 1;
}

void* ma_get (unsigned id)
{
	mem_block* block = head_block;
	while ((block->next != NULL) && (block->id != id))
		block = block->next;
	if (block->id != id) // the wrong id
	{
		errno = EFAULT;
		printf ("ma_get: %s\n", strerror (errno));
		return 0;
	}
	if ((block->id == id) && (0 == block->used)) // we found the block with such id and it's free
	{
		block->used = 1;
		return (block->begin);
	}
	else // we found the block with such id, but it's used now
	{
		return (0);
	}
}

int ma_release(unsigned id)
{
	mem_block* block = head_block;
	while ((block->next != NULL) && (block->id != id))
		block = block->next;
	if ((block->id != id) || ((block->id == id) && (0 == block->used)))  // we found the block with such id, but it is used now
	{                                                                    // or the id is wrong
		errno = EFAULT;
		printf ("ma_release: %s\n", strerror (errno));
		return 0;
	}
	else // we found the block with such id and it's free
	{
		block->used = 0;
		return 1;
	}
}

